﻿using UnityEngine;
using FutureCode.Net;
using FutureCode.Net.SocketClient;
using FutureCode.ObjectPool;
using FutureCode.ObjectPool.Converter;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System;
using FutureCode.Game;
using FutureCode.Game.Command;

public class NetManager : MonoBehaviour
{
    public static NetManager Instance;
    public GameClock Clock;
    public int Ping = 9999;
    public bool Connected = false;
    public string HostIp = "127.0.0.1";
    public int HostPort = 1316;
    public float AdjustInterval = 0.3f;

    GameSettings settings;
    Queue<Action> unprocessActions;
    // Use this for initialization
    void Awake()
    {
        Instance = this;
        bufferManager = new BufferManager<byte>(1 << 8);
        client = new SocketClient(bufferManager);
        client.HostConnected += Client_HostConnected;
        client.HostDisconnected += Client_HostDisconnected;
        client.SendCompleted += Client_SendCompleted;
        client.NewDataReceived += Client_NewDataReceived;
    }
    void Start()
    {
        settings = GameSettings.Instance;
        Clock = new GameClock(DateTime.Now);
        unprocessActions = new Queue<Action>();
    }

    // Update is called once per frame
    void Update()
    {
        lock (unprocessActions)
        {
            if (unprocessActions.Count > 0)
            {
                Action a = unprocessActions.Dequeue();
                a.Invoke();
            }
        }

        if (client.Connected && Time.frameCount % 100 == 0)
        {
            TimeSynchronization();
        }
    }

    void OnApplicationQuit()
    {
        if (client.Connected)
            client.Dispose();
    }

    public void InvokeAtMainThread(Action action)
    {
        lock (unprocessActions)
            unprocessActions.Enqueue(action);
    }

    SocketClient client;
    BufferManager<byte> bufferManager;

    public void ConnectToServer()
    {
        HostIp = settings.HostIP;
        HostPort = int.Parse(settings.HostPort);
        client.Connect(HostIp, HostPort);
    }

    private void Client_NewDataReceived(object sender, NewDataReceivedEventArgs e)
    {
        CommandType cmdType = (CommandType)e.Data.PackageData.GetInt16(4);
        switch (cmdType)
        {
            case CommandType.TimeSynchronization:
                TimeSynchronizationCommand tsc = CommandSerializer.Deserialize<TimeSynchronizationCommand>(e.Data.PackageData);
                int ping = (int)((DateTime.Now - tsc.ClientTime).TotalMilliseconds);
                ping /= 2;
                DateTime serverTime = tsc.ServerTime.AddMilliseconds(ping);
                this.Ping = ping;
                Clock = new GameClock(serverTime);
                break;
            case CommandType.Connected:
                ConnectedCommand cc = CommandSerializer.Deserialize<ConnectedCommand>(e.Data.PackageData);
                GameSettings.Instance.IsPlayerClient = cc.IsPlayer;
                this.Connected = true;
                break;
            case CommandType.PlayerStateChanged:
                PlayerStateChangedCommand pscc = CommandSerializer.Deserialize<PlayerStateChangedCommand>(e.Data.PackageData);
                InvokeAtMainThread(() =>
                {
                    settings.Player.SendMessage("PlayerStateChanged", pscc);
                });
                break;
            case CommandType.MonsterStateChanged:
                MonsterStateChangedCommand mscc = CommandSerializer.Deserialize<MonsterStateChangedCommand>(e.Data.PackageData);
                InvokeAtMainThread(() =>
                {
                    MonsterManager.Instance.MonsterStateChanged(mscc);
                });
                break;
            case CommandType.AdjustIntervalChanged:
                AdjustIntervalChangedCommand acc = CommandSerializer.Deserialize<AdjustIntervalChangedCommand>(e.Data.PackageData);
                AdjustInterval = acc.NewInterval;
                break;
            case CommandType.MaxMonsterCountChanged:
                MaxMonsterCountChangedCommand mmccc = CommandSerializer.Deserialize<MaxMonsterCountChangedCommand>(e.Data.PackageData);
                MonsterManager.Instance.MaxMonsterCount = mmccc.MaxCount;
                break;
            case CommandType.WhetherEnableSyncStrategy:
                WhetherEnableSyncStrategyCommand wessc = CommandSerializer.Deserialize<WhetherEnableSyncStrategyCommand>(e.Data.PackageData);
                settings.EnableSyncStrategy = wessc.EnableSyncStrategy;
                InvokeAtMainThread(() =>
                {
                    ShadowManager.Instance.EnableShadows(wessc.EnableSyncStrategy);
                });
                break;
            case CommandType.WhetherShowShadows:
                WhetherShowShadowsCommand wssc = CommandSerializer.Deserialize<WhetherShowShadowsCommand>(e.Data.PackageData);
                InvokeAtMainThread(() =>
                {
                    ShadowManager.Instance.ShowShadow(wssc.Show);
                });
                break;
            case CommandType.DestoryAllMonster:
                InvokeAtMainThread(() =>
                {
                    MonsterManager.Instance.DestoryAllMonster();
                });
                break;
            case CommandType.RequestPlayerState:
                RequestPlayerStateCommand rpsc = CommandSerializer.Deserialize<RequestPlayerStateCommand>(e.Data.PackageData);
                if (settings.IsPlayerClient)
                {
                    InvokeAtMainThread(() =>
                    {
                        Transform player = settings.Player.transform;
                        rpsc.PositionX = player.position.x;
                        rpsc.PositionY = player.position.y;
                        rpsc.PositionZ = player.position.z;
                        Send(rpsc);
                    });

                }
                else
                {
                    Vector3 position = new Vector3(rpsc.PositionX, rpsc.PositionY, rpsc.PositionZ);
                    InvokeAtMainThread(() =>
                    {
                        GameController.Instance.CreateSoldier(position);
                    });
                }
                break;
        }
        bufferManager.Free(e.Data.PackageData);
    }

    private void Client_SendCompleted(object sender, EventArgs e)
    {
    }

    private void Client_HostDisconnected(object sender, EventArgs e)
    {
    }

    private void Client_HostConnected(object sender, EventArgs e)
    {
        TimeSynchronization();
    }

    public void Send(ICommand cmd)
    {
        if (!Connected)
            return;
        var buffer = cmd.Serialize(bufferManager);
        client.Send(buffer);
    }
    private void TimeSynchronization()
    {
        TimeSynchronizationCommand cmd = new TimeSynchronizationCommand();
        cmd.ClientTime = DateTime.Now;
        Send(cmd);
    }

    public IEnumerator SyncSmoothDrag(CharacterController controller, Vector3 targetPos, float timeDuration, Action callBack = null)
    {
        float startTime = Time.time;
        float endTime = Time.time + timeDuration;
        float usedTime = 0f;
        while (Time.time < endTime)
        {
            Debug.Log(Time.time);
            float percentage = usedTime / timeDuration;
            controller.transform.position = Vector3.Lerp(controller.transform.position, targetPos, percentage);
            yield return new WaitForEndOfFrame();
            usedTime = Time.time - startTime;
        }
        if (callBack != null)
            callBack.Invoke();
    }
}
